<?php declare(strict_types=1);
/**
 * @author      xianganyall <xianganyall@gmail.com>
 * @copyright   2023-2025 owner
 **/

namespace Srv\Libs\Daemon;

use Srv\Libs\Boot;
use Srv\Libs\Common\CommTime;
use Srv\Libs\Frame\Conf;
use Srv\Libs\Frame\Logs;
use Srv\Libs\Tools\ShellPrint;

abstract class PhpDaemon
{
    protected string $pidUrl            = 'phpDaemon.pid';      // PID文件路径
    protected string $logUrl            = 'phpDaemon.log';      // 输出日志路径
    protected string $confUrl           = '';                   // 配置路径
    protected int $pid                  = 0;                    // PID
    protected int $sleep                = 1;                    // sleep时间[秒]
    protected array $initHandle         = [];                   // 初始化事件
    protected array $exeHandle          = [];                   // 执行事件
    protected array $cliArgv            = [];                   // 参数
    protected string $programPath       = '';                   // 执行命令
    protected string $runName           = 'run';                // 参数名称
    protected string $confName          = 'conf';               // 参数名称
    protected string $pidName           = 'pid';                // 参数名称
    protected string $logName           = 'log';                // 参数名称
    protected string $secName           = 'sec';                // 参数名称
    protected string $defaultPidPath    = '';                   // 默认PID路径
    protected string $defaultLogPath    = '';                   // 默认LOG路径
    protected string $defaultConfPath   = '';                   // 默认配置路径
    protected int $defaultSleep         = 1;                    // 默认休眠时长[秒]

    /**
     * __construct
     */
    public function __construct()
    {
        register_shutdown_function(function(){ Logs::flushAll(); });
        $this->toArgv();
    }

    /**
     * runInit
     */
    abstract public function runInit():void;

    /**
     * run
     */
    abstract public function run():void;

    /**
     *
     * toArgv
     */
    private function toArgv():void
    {
        $this->programPath  = Boot::getProgramPath();
        $this->cliArgv      = Conf::getCliArgvList();
        $action             = $this->cliArgv[$this->runName] ?? '';
        // confPath
        $confNamePath       = $this->cliArgv[$this->confName] ?? '';
        (strlen($confNamePath) > 0) && $this->confUrl = $confNamePath;
        // pidPath
        $pidNamePath        = $this->cliArgv[$this->pidName] ?? '';
        (strlen($pidNamePath) > 0) && $this->pidUrl = $pidNamePath;
        // logPath
        $logNamePath        = $this->cliArgv[$this->logName] ?? '';
        (strlen($logNamePath) > 0) && $this->logUrl = $logNamePath;
        // secTime
        $secNameNum         = intval($this->cliArgv[$this->secName] ?? -1);
        ($secNameNum >= 0) && $this->sleep = min(max($secNameNum, 0), 2592000); // max:30 days
        // RunAction
        switch($action){
            case 'start':   { $this->start(); break; }
            case 'stop':    { $this->stop(); break; }
            case 'restart': { $this->restart(); break; }
            case 'status':  { $this->status(); break; }
            case 'reload':  { $this->reload(); break; }
            case 'help':    { $this->help(); break; }
            default:{
                ShellPrint::printlnString('Use: '.$this->programPath.' -- --'.$this->runName.'={start|stop|restart|status|reload|help}', ShellPrint::COLOR_RED);
                exit;
            }
        }
    }

    /**
     * @return void
     * start
     */
    private function start():void
    {
        $this->getPid();
        if($this->isRunByPid($this->pid)){
            $this->logs("Run Start ... [Is Running]\n");
            ShellPrint::printlnString('Run Start ... [Is Running]', ShellPrint::COLOR_GREEN);
            return;
        }
        if(count($this->initHandle) < 1) $this->initHandle  = [[$this, 'runInit'], []];
        if(count($this->exeHandle) < 1) $this->exeHandle    = [[$this, 'run'], []];
        PhpDaemonRun::$initHandle   = $this->initHandle;
        PhpDaemonRun::$exeHandle    = $this->exeHandle;
        PhpDaemonRun::$pidUrl       = $this->pidUrl;
        PhpDaemonRun::$logUrl       = $this->logUrl;
        PhpDaemonRun::$confUrl      = $this->confUrl;
        PhpDaemonRun::$sleep        = $this->sleep;
        $this->logs("Run Starting ...\n");
        ShellPrint::printlnString('Run Starting ...', ShellPrint::COLOR_GREEN);
        PhpDaemonRun::start($this);
        if($this->getPid() && $this->isRunByPid($this->pid)){
            $this->logs("Run Start ... [Success]\n");
            ShellPrint::printlnString('Run Start ... [Success]', ShellPrint::COLOR_GREEN);
        }else{
            $this->logs("Run Start ... [Failed]\n");
            ShellPrint::printlnString('Run Start ... [Failed]', ShellPrint::COLOR_RED);
        }
    }

    /**
     * @return void
     * stop
     */
    private function stop():void
    {
        $this->getPid();
        if($this->isRunByPid($this->pid)){
            posix_kill($this->pid, SIGKILL);
            sleep(2);
            $isRun      = $this->isRunByPid($this->pid);
            if($isRun){
                $this->logs("Run Stop ... [Failed]\n");
                ShellPrint::printlnString('Run Stop ... [Failed]', ShellPrint::COLOR_RED);
            }else{
                @unlink($this->pidUrl);
                $this->logs("Run Stop ... [Success]\n");
                ShellPrint::printlnString('Run Stop ... [Success]', ShellPrint::COLOR_GREEN);
            }
        }else{
            @unlink($this->pidUrl);
            $this->logs("Run Stop ... [Not Running]\n");
            ShellPrint::printlnString('Run Stop ... [Not Running]', ShellPrint::COLOR_GREEN);
        }
    }

    /**
     * @return void
     * status
     */
    private function status():void
    {
        $this->getPid();
        if($this->isRunByPid($this->pid)){
            $this->logs("Run Status ... [Is Running]\n");
            ShellPrint::printlnString('Run Status ... [Is Running]', ShellPrint::COLOR_GREEN);
        }else{
            $this->logs("Run Status ... [Not Running]\n");
            ShellPrint::printlnString('Run Status ... [Not Running]', ShellPrint::COLOR_GREEN);
        }
    }

    /**
     * @return void
     * reload
     */
    private function reload():void
    {
        $this->getPid();
        if($this->isRunByPid($this->pid)){
            posix_kill($this->pid, SIGHUP);
        }else{
            ShellPrint::printlnString('ERR: [Not Running]', ShellPrint::COLOR_RED);
        }
    }

    /**
     * @return void
     * restart
     */
    private function restart():void
    {
        $this->stop();
        $this->start();
    }

    /**
     * @return bool
     * getPid
     */
    private function getPid():bool
    {
        $pidNum     = @file_get_contents($this->pidUrl);
        if(!is_string($pidNum)) return false;
        $pidNum     = trim($pidNum);
        if(strlen($pidNum) > 0) $this->pid = intval($pidNum);
        if($this->pid < 2) return false;
        return true;
    }

    /**
     * @param int $pid
     * @return bool
     * isRunByPid
     */
    private function isRunByPid(int $pid):bool
    {
        if($pid < 2) return false;
        $num = intval(shell_exec('ps -p '.$pid.'|grep -i php|wc -l'));
        return ($num === 1);
    }

    /**
     * @return void
     * help
     */
    protected function help():void
    {
        ShellPrint::printlnString('Usage: ', ShellPrint::COLOR_RED);
        ShellPrint::printlnString('php '.$this->programPath, ShellPrint::COLOR_RED);
        ShellPrint::printlnString('  --'.$this->runName.'=help      #This help', ShellPrint::COLOR_RED);
        ShellPrint::printlnString('', ShellPrint::COLOR_RED);
        ShellPrint::printlnString('  --'.$this->confName.'=<file>   #Configure File Path. default: '.$this->defaultConfPath, ShellPrint::COLOR_CYAN);
        ShellPrint::printlnString('  --'.$this->logName.'=<file>    #Logs File Path. default: '.$this->defaultLogPath, ShellPrint::COLOR_CYAN);
        ShellPrint::printlnString('  --'.$this->pidName.'=<file>    #Pid File Path. default: '.$this->defaultPidPath, ShellPrint::COLOR_CYAN);
        ShellPrint::printlnString('  --'.$this->secName.'=<num>     #Interval Execution Time[second]. default: 1', ShellPrint::COLOR_CYAN);
        ShellPrint::printlnString('', ShellPrint::COLOR_RED);
        ShellPrint::printlnString('  --'.$this->runName.'=start     #Start Service.', ShellPrint::COLOR_RED);
        ShellPrint::printlnString('  --'.$this->runName.'=stop      #Stop Service.', ShellPrint::COLOR_RED);
        ShellPrint::printlnString('  --'.$this->runName.'=restart   #ReStart Service.', ShellPrint::COLOR_RED);
        ShellPrint::printlnString('  --'.$this->runName.'=status    #Show Service status.', ShellPrint::COLOR_RED);
        ShellPrint::printlnString('  --'.$this->runName.'=reload    #reload Configure.', ShellPrint::COLOR_RED);
        ShellPrint::printlnString('', ShellPrint::COLOR_RED);
    }

    /**
     * @param string $string
     * @return bool
     * logs
     */
    public function logs(string $string):bool
    {
        if(function_exists('posix_getpid')){
            $pidInt = posix_getpid();
        }else if(function_exists('getmypid')){
            $pidInt = getmypid();
        }else{
            $pidInt = 0;
        }
        $logString  = date('['.CommTime::getDateTimeLogs().']['.$pidInt.']').$string;
        $pathDir    = dirname($this->logUrl);
        !file_exists($pathDir) && mkdir($pathDir, 0777, true);
        return (bool)(@file_put_contents($this->logUrl, $logString, FILE_APPEND));
    }

    /**
     * @param string $name
     * @return string
     * getArgvStr
     */
    public function getArgvStr(string $name):string
    {
        $name           = trim($name);
        if(strlen($name) < 1) return '';
        return strval(Conf::getCliArgvList()[$name]??'');
    }

    /**
     * @param string $name
     * @return int
     * getArgvInt
     */
    public function getArgvInt(string $name):int
    {
        $name           = trim($name);
        if(strlen($name) < 1) return 0;
        return intval(Conf::getCliArgvList()[$name]??0);
    }
}